package org.dyndns.opendemogroup.optimizer.selections;

import static org.junit.Assert.*;

import java.util.Random;

import org.dyndns.opendemogroup.optimizer.Member;
import org.dyndns.opendemogroup.optimizer.Population;
import org.dyndns.opendemogroup.optimizer.problems.OneMax;
import org.junit.Test;

/**
 * A class to test the class RouletteWheel
 */
public class RouletteWheelTest
{

	/**
	 * Tests the <i>rouletteSpin</i> method with a simulation of a few runs to
	 * make sure that the selections are appropriately distributed.
	 */
	@Test
	public void rouletteSpin_Simulation ( )
	{
		// { initialization
		// make sure the random number sequence is predictable
		Random randomSource = new Random ( 8 * 6 );
		int numberOfAlleles = 100;
		OneMax problem = new OneMax ( numberOfAlleles );
		int[] distribution =
		{
			8, 17, 25, 50
		};
		// { double-check 'distribution'
		int sum = 0;
		int currentFitness = distribution[0];
		for ( int i = 0; i < distribution.length; i++ )
		{
			sum += distribution[i];
			assertTrue (
				"Members of 'distribution' must be sorted in ascending order!",
				distribution[i] >= currentFitness );
			currentFitness = distribution[i];
		}
		assertEquals ( numberOfAlleles, sum );
		// }
		Population p = new Population ( distribution.length, numberOfAlleles );
		for ( int i = 0; i < distribution.length; i++ )
		{
			populateMemberForFitness ( distribution[i], p.members[i] );
		}
		p.computeFitnesses ( problem, null );
		p.sort ( );
		// triple-check total fitness
		assertEquals ( (double) numberOfAlleles, p.getPopulationFitness ( ) );

		int actual;
		int[] hitCounters = new int[distribution.length];
		// }

		// { brute force checks for potential off-by-one errors
		sum = 0;
		for ( int i = 0; i < distribution.length; i++ )
		{
			sum += distribution[i];
			actual =
				RouletteWheel.rouletteSpin ( p, 0, distribution.length, 1,
					sum - 0.1 );
			assertEquals ( i, actual );
			actual =
				RouletteWheel.rouletteSpin ( p, 0, distribution.length, 1, sum );
			assertEquals ( i, actual );
		}
		// }

		// { simulation
		for ( int i = 0; i < numberOfAlleles; i++ )
		{
			double r = randomSource.nextDouble ( ) * numberOfAlleles;
			actual =
				RouletteWheel.rouletteSpin ( p, 0, distribution.length, 1, r );
			hitCounters[actual]++;
		}
		// }

		// { validation
		for ( int i = 0; i < distribution.length; i++ )
		{
			// are they equal within 4%?
			assertEquals ( distribution[i], hitCounters[i], 4.0 );
		}
		// }

	}

	static void populateMemberForFitness ( int targetFitness,
			Member targetMember )
	{
		for ( int i = 0; i < targetFitness; i++ )
		{
			targetMember.genome[i] = 1;
		}
	}
}
